import { EBomCurveType, EGrooveGeometryType, Groove, Number3, Path, Profile } from "@/types";
import { Groups, Materials, OrderDetail } from "@/types/bom";
import { chunk, defaults, difference, groupBy, has, isNil, map, merge, omit, remove, round } from "lodash-es";
import { calBulgeRadius, calExtendPointOnLineSegment, isBulgeCCW, isBulgeMajorArc } from "./math";


export async function transformData({ order, groups, materials }: { order: OrderDetail; groups: Groups; materials: Materials; }) {
  const finishedProductsGroupByProductId = groupBy(materials.finishedProducts, 'productId')
  const materialsItemRecord = getMaterialsItemRecord(materials);
  const bomIdsInGroup: string[] = []

  return {
    "ProjectInfo": order,
    "ProductInfo": groups.filter(groupFilter).map(group => {
      const productId  = group.productId!;
      const finishedProducts = finishedProductsGroupByProductId[productId];
      return {
        "Category": group.category,
        ...transformSpecialField(group),
        "ProductName": group.productName,
        "Matter": group.attributes.baseMaterial,
        "Color": group.attributes.material,
        "Count": 1,
        "ProductHeight": group.attributes.length,
        "ProductWidth": group.attributes.width,
        "ProductDepth": group.attributes.thickness,
        "Bom": (transformGroup({ group, materials, materialsItemRecord, bomIdsInGroup }) ?? [])
          .concat(
            difference((map(finishedProducts, 'finishedId')), bomIdsInGroup).map((bomId) => {
              const materialsItem = materialsItemRecord[bomId]
              return transformMaterialsItem(materialsItem);
            })
          )
      };
    })
  };
}
function groupFilter(group: Group) {
  return (group.attributes.source as string).includes(group.productId!)
}
type MaterialsItem = Materials[keyof Materials][number];
function transformPlank(materialsItem: MaterialsPlank) {
  const groupedGrooves: Record<EGrooveGeometryType, Groove[]> = defaults(groupBy(materialsItem.grooves, 'geometryType'), {
    [EGrooveGeometryType.PATH]: [],
    [EGrooveGeometryType.ROUND_CORNER]: [],
    [EGrooveGeometryType.SQUARE]: [],
    [EGrooveGeometryType.SPECIAL_SHAPED]: [],
  });
  const origin = negatePoint(getLeftBackBottomPointOfPlank(materialsItem));
  return {
    "Hole": materialsItem.holes.map(hole => {
      return ({
        "Face": hole.plankFaceId,
        ...transformPoint(hole.start, origin),
        "Diameter": hole.diameter,
        "Depth": hole.depth
      });
    }),
    "Groove": groupedGrooves[EGrooveGeometryType.PATH].map(groove => ({
      "Face": groove.plankFaceId,
      "ToolOffset": "M",
      "ToolNumber": transformToolNo(groove),
      "Width": groove.width,
      "Depth": groove.depth,
      "ToolPaths": transformPath(shouldExtendPath(groove) ? extendPath({ path: groove.path! }) : groove.path!, origin),
    })),
    "StandardPocket": [...groupedGrooves[EGrooveGeometryType.SQUARE], ...groupedGrooves[EGrooveGeometryType.ROUND_CORNER]].map(groove => {
      return ({
        "Face": groove.plankFaceId,
        "ToolPaths": [
          {
            ...transformPoint(groove.start!, origin),
            "PathType": "KP",
          },
          {
            ...transformPoint(groove.end!, origin),
            "PathType": "KL"
          }
        ],
        "Width": groove.width,
        "Depth": groove.depth,
        "CornerRadius": groove.radius,
        "ToolNumber": transformToolNo(groove),
      });
    }),
    "Pocket": groupedGrooves[EGrooveGeometryType.SPECIAL_SHAPED].map(groove => ({
      "Face": groove.plankFaceId,
      "ToolPaths": transformProfile(groove.profile!, origin),
      "Depth": groove.depth,
      "ToolNumber": transformToolNo(groove),
    })),
    "Aperture": (materialsItem.inners || []).map(inner => ({
      "ToolPaths": transformProfile(inner, origin),
    })),
    "Calibration": transformProfile(materialsItem.finishedProfile, origin)
  };
}

function transformToolNo(groove: Groove) {
  // @ts-expect-error
  return groove.processingTechnology?.toolNo || null
}

function shouldExtendPath({ name }: { name: string }) {
  return name === '免拉手'
}
function extendPath({ path, distance = 50 }: { path: Path, distance?: number }): Path {
  const startLineSeg = { start: { x: path.points[0], y: path.points[1] }, end: { x: path.points[2], y: path.points[3] } };
  const endLineSeg = {
    end: {
      x: path.points[path.points.length - 2],
      y: path.points[path.points.length - 1]

    },
    start: {
      x: path.points[path.points.length - 4],
      y: path.points[path.points.length - 3]
    }
  };
  const expanedStartPoint = calExtendPointOnLineSegment({ start: startLineSeg.end, end: startLineSeg.start, distance })
  const expanedEndPoint = calExtendPointOnLineSegment({ ...endLineSeg, distance })
  return {
    points: [expanedStartPoint.x, expanedStartPoint.y, ...path.points, expanedEndPoint.x, expanedEndPoint.y],
    curves: [EBomCurveType.LineSeg, ...path.curves, EBomCurveType.LineSeg]
  } as Path
}

function getMaterialsItemRecord(materials: Materials) {
  const materialsIdMap: Record<string, MaterialsItem> = {};
  materials.planks.forEach(item => {
    materialsIdMap[item.plankId] = item;
  });
  materials.moldings.forEach(item => {
    materialsIdMap[item.modelId] = item;
  });
  materials.finishedProducts.forEach(item => {
    materialsIdMap[item.finishedId] = item;
  });
  return materialsIdMap;
}
type MaterialsPlank = Materials["planks"][number];
function isMaterialsPlank(materialsItem: MaterialsItem): materialsItem is MaterialsPlank {
  return has(materialsItem, 'plankId');
}
function getLeftBackBottomPointOfPlank({ finishedWidth, finishedHeight, finishedProfile, thickness }: Pick<MaterialsPlank, 'finishedWidth' | 'finishedHeight' | 'finishedProfile' | 'thickness'>) {
  const z = -thickness / 2
  if (finishedProfile) {
    return {
      x: Math.min(...mapPoints(finishedProfile.points, item => item.x)),
      y: Math.min(...mapPoints(finishedProfile.points, item => item.y)),
      z,
    };
  }
  return {
    x: -finishedWidth / 2,
    y: -finishedHeight / 2,
    z
  };
}
function transformPath(profile: Profile | null, origin: Origin, closed = false) {
  if (!profile) {
    return null;
  }
  const { points, curves } = profile;
  let lastPoint: Number2;
  const result = mapPoints(points, (point, i) => {
    let item
    if (i === 0) {
      item = {
        ...transformPoint(point, origin),
        "PathType": "KP"
      };
    } else {
      const curve = curves[i - 1];
      if (curve.type === EBomCurveType.Arc) {
        curve;
      }
      item = {
        ...transformPoint(point, origin),
        "PathType": curve.type === EBomCurveType.LineSeg ? "KL" : "KA",
        ...hasBulge(curve) && {
          "DS": {
            [mergeArcParams(!ArcParms.CCW, !ArcParms.MajorArc)]: 0,
            [mergeArcParams(ArcParms.CCW, !ArcParms.MajorArc)]: 1,
            [mergeArcParams(!ArcParms.CCW, ArcParms.MajorArc)]: 2,
            [mergeArcParams(ArcParms.CCW, ArcParms.MajorArc)]: 3
          }[mergeArcParams(isBulgeCCW(curve.bulge) && ArcParms.CCW, isBulgeMajorArc(curve.bulge) && ArcParms.MajorArc)],
          "R": round(calBulgeRadius(lastPoint, point, curve.bulge), 3)
        }
      };
      lastPoint = point
      return item
    }
    lastPoint = point
    return item
  }, closed);
  return result;
}
function transformProfile(profile: Profile | null, origin: Origin) {
  return transformPath(profile, origin, true)
}
enum ArcParms {
  CCW = 1 << 0,
  MajorArc = 1 << 1
}
function mergeArcParams(...paramsList: (ArcParms | false)[]) {
  return paramsList.reduce((result, item) => result | (item ? item : 0), 0);
}
function hasBulge(curve: any): curve is { bulge: number; } {
  return has(curve, 'bulge');
}
type MaybeNumber3 = Pick<Number3, 'x' | 'y'> & Partial<Pick<Number3, 'z'>>;
type Origin = Partial<Number3>;
function getPointAtOrigin<T extends MaybeNumber3>(point: T, origin: Origin): T {
  return {
    x: origin.x ? point.x + origin.x : point.x,
    y: origin.y ? point.y + origin.y : point.y,
    ...!isNil(point.z) && { z: origin.z ? point.z + origin.z : point.z }
  } as T;
}
type Number2 = Pick<Number3, 'x' | 'y'>;
function mapPoints<T extends any>(points: number[], cb: (item: Number2, i: number) => T, closed = false): T[] {
  let lastIndex = 0;
  const result = chunk(points, 2).map(([x, y], i) => {
    lastIndex = i;
    return cb({ x, y }, i);
  });
  if (closed) {
    result.push(
      cb({ x: points[0], y: points[1] }, lastIndex + 1)
    );
  }
  return result;
}
function transformPoint<T extends MaybeNumber3>(...params: Parameters<typeof getPointAtOrigin<T>>) {
  const { x, y, z } = getPointAtOrigin(...params);
  return {
    "X": round(x, 3),
    "Y": round(y, 3),
    "Z": !isNil(z) ? round(z, 3) : 0,
  };
}
type FourEdges = MaterialsPlank['fourEdges'];
function transformEdges(fourEdges: FourEdges, isRectPlank = true) {
  // @ts-expect-error
  const [edge1, edge2, edge3, edge4] = fourEdges.map(edge => edge.bandThickness);
  if (isRectPlank) {
    return {
      "FrontEdge": edge2,
      "BackEdge": edge4,
      "LeftEdge": edge3,
      "RightEdge": edge1,
    };
  } else {
    return {
      "FrontEdge": edge4,
      "BackEdge": edge2,
      "LeftEdge": edge3,
      "RightEdge": edge1,
    };
  }
}
function isRectPlank(plank: MaterialsPlank) {
  return !plank.finishedProfile;
}
function negatePoint({ x, y, z }: MaybeNumber3) {
  return {
    x: -x, y: -y, ...!isNil(z) && { z: -z }
  }
}

type Group = Groups[number]
type MaterialsItemRecord = ReturnType<typeof getMaterialsItemRecord>;
function transformGroup({ group, materials, materialsItemRecord, bomIdsInGroup }: { group: Group, materials: Materials, materialsItemRecord: MaterialsItemRecord, bomIdsInGroup: string[] }): any {
  return group.children?.map((group) => {
    const { bomId } = group;
    const materialsItem = bomId ? materialsItemRecord[bomId] : group as unknown as MaterialsItem;
    bomId && bomIdsInGroup.push(bomId);
    const info = transformMaterialsItem(materialsItem)
    const Bom: any[] = transformGroup({ group, materials, materialsItemRecord, bomIdsInGroup })
    const target = Bom?.find(item => item.Category === '0301')
    // 提升Category 0301部分字段到 0309  
    if (target) {
      merge(info, omit(target, 'BomName', 'Category', 'OpenDirection', 'Remark'))
      remove(Bom, (item) => {
        return item === target
      })
    }
    return {
      ...info,
      "Bom": Bom
    }
  })
}

function transformMaterialsItem(materialsItem: MaterialsItem) {
  const { comment, code, name, category, attributes } = materialsItem;
  const isPlank = isMaterialsPlank(materialsItem);

  return {
    "BomName": name,
    "Category": category,
    ...transformSpecialField(materialsItem),
    ...isPlank ? {
      "Matter": materialsItem.baseMaterial,
      "Color": materialsItem.material,
      "Length": materialsItem.finishedHeight,
      "Width": materialsItem.finishedWidth,
      "Thickness": materialsItem.thickness,
      "OpenDirection": materialsItem.openDirection ?? attributes.openDirection,
      "FrontMaterial": materialsItem.frontMaterial,
      "BackMaterial": materialsItem.backMaterial,
      "TextureDirection": {
        HORIZONTAL: "横纹",
        VERTICAL: "竖纹",
        NONE: "无纹理"
      }[materialsItem.textureDirection],
    } : {
      "Matter": attributes.material,
      "Color": attributes.baseMaterial,
      "Length": attributes.length,
      "Width": attributes.width,
      "Thickness": attributes.thickness,
      "OpenDirection": attributes.openDirection,
    },
    "Count": hasQuantity(materialsItem) ? materialsItem.quantity : 1,
    "Remark": comment,
    "BarCode": code ?? materialsItem.attributes.code,
    ...isPlank && transformEdges(materialsItem.fourEdges, isRectPlank(materialsItem)),
    "MachineInfo": isPlank ? transformPlank(materialsItem) : null,
  }
}
function transformSpecialField({ comment, codeInERP, category }: Pick<MaterialsItem, 'comment' | 'codeInERP' | 'category'>) {
  const codeInERPMap: Record<string, string> = {
    '0405': '结构五金',
    '0406': '功能五金',
  }
  return {
    Brand: comment?.includes("#") ? comment?.split("#", 1)[0] : null,
    CodeInERP: codeInERPMap[category] || codeInERP,
  }
}

function hasQuantity(value: any): value is { quantity: number } {
  return has(value, 'quantity');
}



